package com.dnjn.happiness.system.sensitive;


import com.dnjn.happiness.common.core.constant.HttpStatus;
import com.dnjn.happiness.common.core.utils.StringUtils;
import com.dnjn.happiness.common.core.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class DesensitizedUtils<T> {

    /**
     * 脱敏数据列表
     */
    private List<T> list;

    /**
     * 注解列表
     */
    private List<Object[]> fields;

    /**
     * 实体对象
     */
    public Class<T> clazz;


    public DesensitizedUtils(Class<T> clazz) {
        this.clazz = clazz;
    }

    /**
     * 初始化数据
     *
     * @param list 需要处理数据
     */
    public void init(List<T> list) {
        if (list == null) {
            list = new ArrayList<T>();
        }
        this.list = list;

        // 得到所有定义字段
        createSensitiveField();
    }

    /**
     * 初始化数据
     *
     * @param t 需要处理数据
     */
    public void init(T t) {

        list = new ArrayList<T>();

        if (t != null) {
            list.add(t);
        }

        // 得到所有定义字段
        createSensitiveField();
    }

    /**
     * 得到所有定义字段
     */
    private void createSensitiveField() {
        this.fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(SensitiveEncrypt.class)) {
                putToField(field, field.getAnnotation(SensitiveEncrypt.class));
            }

            // 多注解
//            if (field.isAnnotationPresent(Excels.class))
//            {
//                Excels attrs = field.getAnnotation(Excels.class);
//                Excel[] excels = attrs.value();
//                for (Excel excel : excels)
//                {
//                    putToField(field, excel);
//                }
//            }
        }
    }

    /**
     * 对list数据源将其里面的数据进行脱敏处理
     *
     * @param list
     * @return 结果
     */
    public AjaxResult desensitizedList(List<T> list) {

        if (list == null) {
            return AjaxResult.error("脱敏数据为空");
        }

        // 初始化数据
        this.init(list);

        int failTimes = 0;

        for (T t : this.list) {
            if ((Integer) desensitization(t).get("code") != HttpStatus.SUCCESS) {
                failTimes++;
            }
        }

        if (failTimes > 0) {
            return AjaxResult.error("脱敏操作中出现失败", failTimes);
        }

        return AjaxResult.success();
    }

    /**
     * 放到字段集合中
     */
    private void putToField(Field field, SensitiveEncrypt attr) {
        if (attr != null) {
            this.fields.add(new Object[]{field, attr});
        }
    }

    /**
     * 脱敏：JavaBean模式脱敏
     *
     * @param t 需要脱敏的对象
     * @return
     */
    public AjaxResult desensitization(T t) {
        if (t == null) {
            return AjaxResult.error("脱敏数据为空");
        }

        // 初始化数据
        init(t);

        try {
            // 遍历处理需要进行 脱敏的字段
            for (Object[] os : fields) {
                Field field = (Field) os[0];
                SensitiveEncrypt sensitiveEncrypt = (SensitiveEncrypt) os[1];
                // 设置实体类私有属性可访问
                field.setAccessible(true);
                desensitizeField(sensitiveEncrypt, t, field);
            }
            return AjaxResult.success(t);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("日志脱敏处理失败，回滚，详细信息:[{}]", e);
            return AjaxResult.error("脱敏处理失败", e);
        }
    }

    /**
     * 对类的属性进行脱敏
     *
     * @param attr  脱敏参数
     * @param vo    脱敏对象
     * @param field 脱敏属性
     * @return
     */
    private void desensitizeField(SensitiveEncrypt attr, T vo, Field field) throws IllegalAccessException {

        if (attr == null || vo == null || field == null) {
            return;
        }

        // 读取对象中的属性
        Object value = field.get(vo);
        SensitiveType sensitiveType = attr.type();
        int prefixNoMaskLen = attr.prefixNoMaskLen();
        int suffixNoMaskLen = attr.suffixNoMaskLen();
        String symbol = attr.symbol();

        //获取属性后现在默认处理的是String类型，其他类型数据可扩展
        Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        field.set(vo, val);
    }

    /**
     * 以类的属性的get方法方法形式获取值
     *
     * @param o    对象
     * @param name 属性名
     * @return value
     * @throws Exception
     */
    private Object getValue(Object o, String name) throws Exception {
        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) {
            Class<?> clazz = o.getClass();
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            o = field.get(o);
        }
        return o;
    }

    /**
     * 根据不同注解类型处理不同字段
     */
    private static Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
        switch (sensitiveType) {
            case CUSTOMER:
                value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
                break;
            case CHINESE_NAME:
                value = chineseName(value, symbol);
                break;
            case ID_CARD_NUM:
                value = idCardNum(value, symbol);
                break;
            case MOBILE_PHONE:
                value = mobilePhone(value, symbol);
                break;
            case FIXED_PHONE:
                value = fixedPhone(value, symbol);
                break;
            case PASSWORD:
                value = password(value, symbol);
                break;
            case BANKCARD:
                value = bankCard(value, symbol);
                break;
            case EMAIL:
                value = email(value, symbol);
                break;
            case ADDRESS:
                value = address(value, symbol);
                break;
        }
        return value;
    }

    /*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/

    /**
     * 【自定义】 根据设置进行配置
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }

        return value;
    }

    /**
     * 对字符串进行脱敏处理
     *
     * @param s               需处理数据
     * @param prefixNoMaskLen 开头展示字符长度
     * @param suffixNoMaskLen 结尾展示字符长度
     * @param symbol          填充字符
     * @return
     */
    static private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
        // 是否为空
        if (StringUtils.isBlank(s)) {
            return "";
        }

        // 如果设置为空之类使用 * 代替
        if (StringUtils.isBlank(symbol)) {
            symbol = "*";
        }

        // 对长度进行判断
        int length = s.length();
        if (length > prefixNoMaskLen + suffixNoMaskLen) {
            String namePrefix = StringUtils.left(s, prefixNoMaskLen);
            String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
            s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
        }

        return s;
    }

    /**
     * 【中文姓名】只显示第一个汉字，其他隐藏为2个星号，比如：李**
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String chineseName(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示一个字符
            int prefixNoMaskLen = 1;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【身份证号】显示最后四位，其他隐藏。共计18位或者15位，比如：*************1234
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String idCardNum(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 结尾只展示四个字符
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【固定电话】 显示后四位，其他隐藏，比如：*******3241
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String fixedPhone(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 结尾只展示四个字符
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【手机号码】前三位，后四位，其他隐藏，比如：135****6810
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String mobilePhone(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示三个字符  结尾只展示四个字符
            int prefixNoMaskLen = 3;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【地址】只显示到地区，不显示详细地址，比如：湖南省长沙市岳麓区***
     * 只能处理 省市区的数据
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return
     */
    static public String address(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示九个字符
            int prefixNoMaskLen = 9;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }

    /**
     * 【电子邮箱】 邮箱前缀仅显示第一个字母，前缀其他隐藏，用星号代替，@及后面的地址显示，比如：d**@126.com
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String email(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示一个字符  结尾只展示@及后面的地址
            int prefixNoMaskLen = 1;
            int suffixNoMaskLen = 4;

            String s = (String) value;
            if (StringUtils.isBlank(s)) {
                return "";
            }

            // 获取最后一个@
            int lastIndex = StringUtils.lastIndexOf(s, "@");
            if (lastIndex <= 1) {
                return s;
            } else {
                suffixNoMaskLen = s.length() - lastIndex;
            }

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【银行卡号】前六位，后四位，其他用星号隐藏每位1个星号，比如：6222600**********1234
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return 脱敏后数据
     */
    static public String bankCard(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示六个字符  结尾只展示四个字符
            int prefixNoMaskLen = 6;
            int suffixNoMaskLen = 4;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";

    }

    /**
     * 【密码】密码的全部字符都用*代替，比如：******
     *
     * @param value  需处理数据
     * @param symbol 填充字符
     * @return
     */
    static public String password(Object value, String symbol) {

        //针对字符串的处理
        if (value instanceof String) {
            // 对前后长度进行设置 默认 开头只展示六个字符  结尾只展示四个字符
            int prefixNoMaskLen = 0;
            int suffixNoMaskLen = 0;

            return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
        }
        return "";
    }
}
